/* QtLua -- Lua bindings for Qt
   Copyright (C) 2011, Jarek Pelczar

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library; if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA

*/

#include "qluascriptdebuggerlocalsmodel_p.h"
#include "qluascriptcontext_p.h"
#include "qluascriptengine_p.h"
#include "qluascriptcontextinfo_p.h"
#include <QStringList>
#include <QFont>
#include <QBrush>
#include <QColor>

class QLuaScriptDebuggerLocalsModelPrivate
{
public:
    QLuaScriptDebuggerLocalsModelPrivate();
    ~QLuaScriptDebuggerLocalsModelPrivate();

    struct Node {
        int id;
        QString name;
        QString textValue;
        Node * parentNode;
        QList<Node *> childNodes;
        bool m_fetched;
        bool m_fetching;
        QLuaScriptValue m_value;

        inline Node()
            : parentNode(0),
              m_fetched(false),
              m_fetching(false)
        {
        }
    };

    QLuaScriptDebuggerLocalsModel * q;
    Node * m_root;
    int m_lastNodeId;
    QMap<int, Node *> m_nodeIds;

    Node * node_from_index(const QModelIndex& index) const {
        if(!index.isValid())
            return m_root;
        return (Node *)index.internalPointer();
    }

    QModelIndex index_from_node(Node * node) const {
        if(!node || node == m_root)
            return QModelIndex();
        Node * p = node->parentNode;
        int row = p ? p->childNodes.indexOf(node) : 0;
        return q->createIndex(row, 0, node);
    }

    bool is_top_level_node(Node * node) const {
        return node == m_root;
    }

    Node * alloc_node(Node * parent) {
        Node * n = new Node();
        n->id = ++m_lastNodeId;
        n->parentNode = parent;
        if(parent)
            parent->childNodes.append(n);
        return n;
    }
};

QLuaScriptDebuggerLocalsModelPrivate::QLuaScriptDebuggerLocalsModelPrivate()
{
    m_lastNodeId = 0;
    m_root = new Node();
}

QLuaScriptDebuggerLocalsModelPrivate::~QLuaScriptDebuggerLocalsModelPrivate()
{
    qDeleteAll(m_nodeIds);
}

QLuaScriptDebuggerLocalsModel::QLuaScriptDebuggerLocalsModel(QObject *parent) :
    QAbstractItemModel(parent)
{
    d = new QLuaScriptDebuggerLocalsModelPrivate();
    d->q = this;
}

QLuaScriptDebuggerLocalsModel::~QLuaScriptDebuggerLocalsModel()
{
    delete d;
}

void QLuaScriptDebuggerLocalsModel::loadWithContext(const QLuaScriptContextInfo& context)
{
    QHash<QByteArray, int> locals(context.localVariables());
    beginResetModel();
    qDeleteAll(d->m_nodeIds);
    d->m_nodeIds.clear();
    d->m_lastNodeId = 0;
    d->m_root->childNodes.clear();

#ifdef QT_DEBUG
    qDebug() << "Load context" << locals;
#endif

    QHash<QByteArray, int> vars(locals);
    if(!vars.isEmpty()) {
        QHashIterator<QByteArray, int> it(vars);
        while(it.hasNext()) {
            it.next();
            QLuaScriptDebuggerLocalsModelPrivate::Node * n = d->alloc_node(d->m_root);
            n->name = QString::fromUtf8(it.key().constData(), it.key().size());
            QLuaScriptValue value(context.localVariable(it.value()).second);
            if(value.isTable() || value.isUserData()) {
                n->m_value = context.engine()->newWeakTable();
                n->m_value.setProperty("ref", value);
            } else {
                n->m_value = value;
                n->textValue = value.toUnicode();
            }
        }
    }

    endResetModel();
}

QModelIndex QLuaScriptDebuggerLocalsModel::index(int row, int column,
                          const QModelIndex &parent) const
{
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(parent);
    if(row < 0 || row >= node->childNodes.count())
        return QModelIndex();
    return createIndex(row, column, node->childNodes.at(row));
}

QModelIndex QLuaScriptDebuggerLocalsModel::parent(const QModelIndex &child) const
{
    if(!child.isValid())
        return QModelIndex();
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(child);
    return d->index_from_node(node->parentNode);
}

int QLuaScriptDebuggerLocalsModel::rowCount(const QModelIndex &parent) const
{
    const_cast<QLuaScriptDebuggerLocalsModel *>(this)->fetchMore(parent);
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(parent);
    return node ? node->childNodes.count() : 0;
}

int QLuaScriptDebuggerLocalsModel::columnCount(const QModelIndex &parent) const
{
    return 2;
}

QVariant QLuaScriptDebuggerLocalsModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid())
        return QVariant();
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(index);
    if(!node)
        return QVariant();
    if(role == Qt::DisplayRole)
    {
        if(index.column() == 0)
            return node->name;
        if(index.column() == 1) {
            QString str = node->textValue;
            if(str.indexOf(QLatin1Char('\n')) != -1) {
                QStringList lines = str.split(QLatin1Char('\n'));
                int lineCount = lines.size();
                if(lineCount > 1) {
                    lines = lines.mid(0, 1);
                    lines.append(QString::fromLatin1("... %1 more lines ...").arg(lineCount - 1));
                }
                str = lines.join(QLatin1String("\n"));
            }
            return str;
        }
    } else if(role == Qt::EditRole) {
        if((index.column() == 1) && !d->is_top_level_node(node)) {
            QString str = node->textValue;
            if(node->m_value.isString()) {
                str.replace(QLatin1Char('\"'), QLatin1String("\\\""));
                str.prepend(QLatin1Char('\"'));
                str.append(QLatin1Char('\"'));
            }
            return str;
        }
    } else if(role == Qt::ToolTipRole) {
        if(index.column() == 1) {
            QString str = node->textValue;
            if(str.indexOf(QLatin1Char('\n')) != -1)
                return str;
        }
    } else if(role == Qt::BackgroundRole) {
        if(d->is_top_level_node(node))
            return QBrush(Qt::darkGray);
    } else if(role == Qt::TextColorRole) {
        if(d->is_top_level_node(node))
            return QColor(Qt::white);
    } else if(role == Qt::FontRole) {
        if(d->is_top_level_node(node)) {
            QFont fnt;
            fnt.setBold(true);
            return fnt;
        }
    }

    return QVariant();
}

QVariant QLuaScriptDebuggerLocalsModel::headerData(int section, Qt::Orientation orientation,
                            int role) const
{
    if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
        if(section == 0)
            return tr("Name");
        if(section == 1)
            return tr("Value");
    }
    return QVariant();
}

Qt::ItemFlags QLuaScriptDebuggerLocalsModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return 0;
    Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    if(index.column() == 1 && index.parent().isValid()) {
        //QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(index);
        f |= Qt::ItemIsEditable;
    }
    return f;
}

void QLuaScriptDebuggerLocalsModel::fetchMore(const QModelIndex &parent)
{
    if(!parent.isValid())
        return;
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(parent);
    if(d->is_top_level_node(node))
        return;
    if(node->m_fetching || node->m_fetched)
        return;
    node->m_fetching = true;
    if(node->m_value.isTable())
    {

    }
    node->m_fetching = false;
    node->m_fetched = true;
}

bool QLuaScriptDebuggerLocalsModel::canFetchMore(const QModelIndex &parent) const
{
    if(!parent.isValid())
        return false;
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(parent);
    return node &&
            (node->m_value.isTable() && !node->m_fetched && !node->m_fetching);
}

bool QLuaScriptDebuggerLocalsModel::hasChildren(const QModelIndex &parent) const
{
    if(!parent.isValid())
        return !d->m_root->childNodes.isEmpty();
    QLuaScriptDebuggerLocalsModelPrivate::Node * node = d->node_from_index(parent);
    return !d->is_top_level_node(node) &&
           (!node->childNodes.isEmpty() ||
            (node->m_value.isTable() && !node->m_fetched));
}
